using System;
using System.Collections.Generic;
using System.Text;

using System.Collections.Specialized;
using System.Data.SqlClient;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Net.Mail;
using System.Reflection;

namespace XLETL.ExceptionHandling
{
	public abstract class HandlerBase
	{
		#region Handler State
		protected const string _defaultLogName = "UnhandledExceptionLog.txt";
		protected const string _rootException = "System.Web.HttpUnhandledException";
		protected const string _rootWsException = "System.Web.Services.Protocols.SoapException";

		protected string _logFilePath = Config.GetPath( "PathLogFile" );

		protected string _exceptionType = string.Empty;
		protected string _exceptionText = string.Empty;

		protected bool _logToDatabaseOK = false;
		protected bool _logToFileOK = false;
		protected bool _logToEventLogOK = false;

		protected NameValueCollection _results = new NameValueCollection();

		protected static Assembly _parentAssembly = null;
		#endregion

		#region Inputs
		protected static string ApplicationPath
		{
			get { return AppDomain.CurrentDomain.BaseDirectory; }
		}

		protected static DateTime GetAssemblyBuildDate( Assembly a )
		{
			return GetAssemblyBuildDate( a, false );
		}

		protected static DateTime GetAssemblyBuildDate( Assembly asm, bool forceFileDate )
		{
			Version ver = asm.GetName().Version;
			DateTime result;

			if( forceFileDate )
				result = GetAssemblyFileTime( asm );
			else
			{
				result = new DateTime( 2000, 1, 1 ).AddDays( ver.Build ).AddSeconds( ver.Revision * 2 );

				if( TimeZone.IsDaylightSavingTime( result, TimeZone.CurrentTimeZone.GetDaylightChanges( result.Year ) ) )
					result = result.AddHours( 1 );

				if( result > DateTime.Now || ver.Build < 730 || ver.Revision == 0 )
					result = GetAssemblyFileTime( asm );

			}

			return result;
		}

		//
		// exception-safe file attrib retrieval; we don't care if this fails
		//
		protected static DateTime GetAssemblyFileTime( Assembly asm )
		{
			try
			{
				return System.IO.File.GetLastWriteTime( asm.Location );
			}
			catch( Exception )
			{
				return DateTime.MaxValue;
			}
		}

		/// <summary>
		/// exception-safe WindowsIdentity.GetCurrent retrieval; returns "domain\username"
		/// </summary>
		/// <remarks>
		/// per MS, this can sometimes randomly fail with "Access Denied" on NT4
		/// </remarks>
		protected static string CurrentWindowsIdentity()
		{
			try
			{
				return System.Security.Principal.WindowsIdentity.GetCurrent().Name;
			}
			catch( Exception )
			{
				return string.Empty;
			}
		}

		/// <summary>
		/// exception-safe System.Environment "domain\username" retrieval
		/// </summary>
		protected static string CurrentEnvironmentIdentity()
		{
			try
			{
				return System.Environment.UserDomainName + "\\" + System.Environment.UserName;
			}
			catch( Exception )
			{
				return string.Empty;
			}
		}

		/// <summary>
		/// retrieve Process identity with fallback on error to safer method
		/// </summary>
		protected static string ProcessIdentity()
		{
			string strTemp = CurrentWindowsIdentity();

			if( string.IsNullOrEmpty( strTemp ) )
				strTemp = CurrentEnvironmentIdentity();

			return strTemp;
		}
		#endregion

		#region Outputs
		protected bool Write( string source, string prefix, string[] attachmentFiles, Dictionary<string, Stream> attachmentStreams )
		{
			bool result = false;

			if( !WriteToDatabase() )
			{
				result = WriteToEventLog( source );
			}

			result &= WriteToFile();
			result &= WriteToEmail( prefix, attachmentFiles, attachmentStreams );

			return result;
		}

		/// <summary>
		/// write current exception info to a text file; 
		/// requires write permissions for the target folder
		/// </summary>
		protected bool WriteToFile()
		{
			if( string.IsNullOrEmpty( Path.GetFileName( _logFilePath ) ) )
				_logFilePath = Path.Combine( _logFilePath, _defaultLogName );

			StreamWriter sw = null;

			try
			{
				using( sw = new StreamWriter( _logFilePath, true ) )
				{
					sw.WriteLine( "**** - " + DateTime.Now.ToString( "s" ) );
					sw.Write( _exceptionType );
					sw.WriteLine();
					sw.Write( _exceptionText );
					sw.WriteLine();
					sw.Close();
				}

				_logToFileOK = true;
			}
			catch( Exception ex )
			{
				_results.Add( "LogToFile", ex.Message );
			}
			finally
			{
				if( sw != null )
					sw.Close();
			}
			return _logToFileOK;
		}

		/// <summary>
		/// send current exception info via email
		/// </summary>
		protected bool WriteToEmail( string subjectPrefix, string[] attachmentFiles, Dictionary<string,Stream> attachmentStreams )
		{
			string to = Config.GetString( "EmailTo", "" );

			if( string.IsNullOrEmpty( to ) )
			{
				// don't bother mailing if we don't have anyone to mail to..
				//_blnLogToEmail = false;
				return true;
			}

			try
			{
				string host = Config.GetString( "SmtpServer", string.Empty );
				int port = Config.GetInteger( "SmtpPort", 21 );
				string userName = Config.GetString( "SmtpUser", string.Empty );
				string password = Config.GetString( "SmtpPwd", string.Empty );
				string domain = Config.GetString( "SmtpDefaultDomain", string.Empty );
				bool enableSsl = Config.GetBoolean( "SmtpUseSsl", true );

				string from = Config.GetString( "SmtpFromAddress", string.Empty );

				using( MailMessage msg = new MailMessage() )
				{
					msg.From = new MailAddress( from );
					msg.Subject = subjectPrefix + _exceptionType; // "ERROR: " + _exceptionType;
					msg.Body = _exceptionText;
					msg.BodyEncoding = System.Text.Encoding.UTF8;
					msg.IsBodyHtml = false;

					string[] recipients = to.Split( '|', ';', ',' );
					foreach( string recip in recipients )
					{
						if( !string.IsNullOrEmpty( recip.Trim() ) )
						{
							msg.To.Add( recip.Trim() );
						}
					}

					NetworkCredential auth = CredentialCache.DefaultNetworkCredentials;

					if( !string.IsNullOrEmpty( userName ) )
					{
						if( string.IsNullOrEmpty( domain ) )
							auth = new NetworkCredential( userName, password );
						else
							auth = new NetworkCredential( userName, password, domain );
					}

					SmtpClient smtpClient = new SmtpClient( host, port );

					smtpClient.EnableSsl = enableSsl;
					smtpClient.Credentials = auth;
					smtpClient.DeliveryMethod = SmtpDeliveryMethod.Network;

					foreach( string file in attachmentFiles )
					{
						msg.Attachments.Add( new Attachment( file ) );
					}

					foreach( string name in attachmentStreams.Keys )
					{
						msg.Attachments.Add( new Attachment( attachmentStreams[name], name ) );
					}

					smtpClient.Send( msg );

					return true;
				}
			}
			catch( Exception ex )
			{
				_results.Add( "LogToEmail", ex.Message );
				// we're in an unhandled exception handler
			}

			return false;
		}

		protected bool WriteToEventLog( string source )
		{
			try
			{
				EventLog.WriteEntry( source, Environment.NewLine + _exceptionText, EventLogEntryType.Error );

				_logToEventLogOK = true;
			}
			catch( Exception ex )
			{
				_results.Add( "LogToEventLog", ex.Message );
			}
			return _logToEventLogOK;
		}

		public delegate void DatabaseWriteEventHandler( string type, string text );
		public static event DatabaseWriteEventHandler OnWriteToDatabase;

		protected bool WriteToDatabase()
		{
			_logToDatabaseOK = false;

			if( OnWriteToDatabase != null )
			{
				try
				{
					OnWriteToDatabase( _exceptionType, _exceptionText );
					_logToDatabaseOK = true;
				}
				catch( Exception ex )
				{
					_results.Add( "LogToDatabase", ex.ToString() );
				}
			}
			else
			{
				_results.Add( "LogToDatabase", "No subscriptions to OnWriteToDatabase event" );
			}

			return _logToDatabaseOK;
		}
		#endregion

		#region String Conversions
		/// <summary>
		/// turns a single stack frame object into an informative string
		/// </summary>
		/// <param name="sf"></param>
		/// <returns></returns>
		protected string StackFrameToString( StackFrame sf )
		{
			StringBuilder sb = new StringBuilder();
			int intParam;
			MemberInfo mi = sf.GetMethod();

			// build method name
			sb.Append( "	" );
			sb.Append( mi.DeclaringType.Namespace );
			sb.Append( "." );
			sb.Append( mi.DeclaringType.Name );
			sb.Append( "." );
			sb.Append( mi.Name );

			// build method params
			ParameterInfo[] objParameters = sf.GetMethod().GetParameters();

			sb.Append( "( " );
			intParam = 0;
			foreach( ParameterInfo objParameter in objParameters )
			{
				intParam += 1;
				if( intParam > 1 ) sb.Append( ", " );

				sb.Append( objParameter.ParameterType.Name );
				sb.Append( " " );
				sb.Append( objParameter.Name );
			}
			sb.Append( " )" );
			sb.Append( Environment.NewLine );

			// if source code is available, append location info
			sb.Append( "		" );
			if( string.IsNullOrEmpty( sf.GetFileName() ) )
			{
				if( _parentAssembly != null )
					sb.Append( System.IO.Path.GetFileName( _parentAssembly.CodeBase ) );
				else
					sb.Append( "(unknown file)" );

				// native code offset is always available
				sb.Append( ": N " );
				sb.Append( String.Format( "{0:#00000}", sf.GetNativeOffset() ) );
			}
			else
			{
				sb.Append( System.IO.Path.GetFileName( sf.GetFileName() ) );
				sb.Append( ": line " );
				sb.Append( String.Format( "{0:#0000}", sf.GetFileLineNumber() ) );
				sb.Append( ", col " );
				sb.Append( String.Format( "{0:#00}", sf.GetFileColumnNumber() ) );
				// if IL is available, append IL location info
				if( sf.GetILOffset() != System.Diagnostics.StackFrame.OFFSET_UNKNOWN )
				{
					sb.Append( ", IL " );
					sb.Append( String.Format( "{0:#0000}", sf.GetILOffset() ) );
				}
			}
			sb.Append( Environment.NewLine );

			return sb.ToString();
		}

		protected abstract string SysInfoToString();

		/// <summary>
		/// translate exception object to string, with additional system info
		/// </summary>
		/// <param name="exceptn">Exception to be converted</param>
		/// <returns></returns>
		protected string ExceptionToString( Exception ex, bool includeSysInfo )
		{
			StringBuilder builder = new StringBuilder();

			// Inner exceptions are handled recursively
			if( !( ex.InnerException == null ) )
			{
				// sometimes the original exception is wrapped in a more relevant outer exception
				// the detail exception is the "inner" exception
				// see http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnbda/html/exceptdotnet.asp

			// don't return the outer root ASP exception; it is redundant.
				if( ex.GetType().ToString() == _rootException || ex.GetType().ToString() == _rootWsException )
					return ExceptionToString( ex.InnerException, true );
				else
				{
					builder.Append( "(Inner Exception)" );
					builder.Append( Environment.NewLine );
					builder.Append( ExceptionToString( ex.InnerException, false ) );
					builder.Append( Environment.NewLine );
					builder.Append( "(Outer Exception)" );
					builder.Append( Environment.NewLine );
				}
			}

			// get general system and app information
			// we only really want to do this on the outermost exception in the stack
			if( includeSysInfo )
			{
				builder.Append( SysInfoToString() );
				builder.Append( AssemblyInfoToString( ex ) );
				builder.Append( Environment.NewLine );
			}

			// get exception-specific information
			builder.Append( "Exception Source:      " );
			try
			{
				builder.Append( ex.Source );
			}
			catch( Exception e )
			{
				builder.Append( "* Exception Source Error: " + e.ToString() );
			}

			builder.Append( Environment.NewLine );

			builder.Append( "Exception Type:        " );
			try
			{
				builder.Append( ex.GetType().FullName );
			}
			catch( Exception e )
			{
				builder.Append( "* Exception Type Error: " + e.ToString() );
			}

			builder.Append( Environment.NewLine );
			builder.Append( "Exception Message:     " );

			try
			{
				builder.Append( ex.Message );
			}
			catch( Exception e )
			{
				builder.Append( "* Exception Message Error: " + e.ToString() );
			}

			builder.Append( Environment.NewLine );
			builder.Append( "Exception Target Site: " );

			try
			{
				builder.Append( ex.TargetSite.Name );
			}
			catch( Exception e )
			{
				builder.Append( "* Exception Target Site Error: " + e.ToString() );
			}

			builder.Append( Environment.NewLine );

			// Check for specific exception types (and output specific details if known)
			if( ex is SqlException )
			{
				SqlExceptionToString( builder, ex );
			}

			try
			{
				string x = EnhancedStackTrace( ex );
				builder.Append( x );
			}
			catch( Exception e )
			{
				builder.Append( "* Exception Stack Trace Error: " + e.ToString() );
			}

			builder.Append( Environment.NewLine );

			return builder.ToString();
		}

		private void SqlExceptionToString( StringBuilder builder, Exception exceptn )
		{
			SqlException se = exceptn as SqlException;

			builder.Append( Environment.NewLine );
			builder.Append( "SQL Procedure:         " );
			try
			{
				builder.Append( se.Procedure );
			}
			catch( Exception e )
			{
				builder.Append( e.Message );
			}
			builder.Append( Environment.NewLine );

			builder.Append( "SQL Server:            " );
			try
			{
				builder.Append( se.Server );
			}
			catch( Exception e )
			{
				builder.Append( e.Message );
			}
			builder.Append( Environment.NewLine );

			builder.Append( "SQL State:             " );
			try
			{
				builder.Append( se.State );
			}
			catch( Exception e )
			{
				builder.Append( e.Message );
			}
			builder.Append( Environment.NewLine );

			builder.Append( "SQL Source:            " );
			try
			{
				builder.Append( se.Source );
			}
			catch( Exception e )
			{
				builder.Append( e.Message );
			}
			builder.Append( Environment.NewLine );

			builder.Append( "SQL Error Number:      " );
			try
			{
				builder.Append( se.Number );
			}
			catch( Exception e )
			{
				builder.Append( e.Message );
			}
			builder.Append( Environment.NewLine );

			builder.Append( "SQL Line Number:       " );
			try
			{
				builder.Append( se.LineNumber );
			}
			catch( Exception e )
			{
				builder.Append( e.Message );
			}
			builder.Append( Environment.NewLine );

			foreach( SqlError error in se.Errors )
			{
				builder.Append( "SQL Error Class:       " );
				try
				{
					builder.Append( error.Class );
				}
				catch( Exception e )
				{
					builder.Append( e.Message );
				}
				builder.Append( Environment.NewLine );

				builder.Append( "SQL Error Line Number: " );
				try
				{
					builder.Append( error.LineNumber );
				}
				catch( Exception e )
				{
					builder.Append( e.Message );
				}
				builder.Append( Environment.NewLine );

				builder.Append( "SQL Error Message:     " );
				try
				{
					builder.Append( error.Message );
				}
				catch( Exception e )
				{
					builder.Append( e.Message );
				}
				builder.Append( Environment.NewLine );

				builder.Append( "SQL Error Number:      " );
				try
				{
					builder.Append( error.Number );
				}
				catch( Exception e )
				{
					builder.Append( e.Message );
				}
				builder.Append( Environment.NewLine );

				builder.Append( "SQL Error Procedure:   " );
				try
				{
					builder.Append( error.Procedure );
				}
				catch( Exception e )
				{
					builder.Append( e.Message );
				}
				builder.Append( Environment.NewLine );

				builder.Append( "SQL Error Server:      " );
				try
				{
					builder.Append( error.Server );
				}
				catch( Exception e )
				{
					builder.Append( e.Message );
				}
				builder.Append( Environment.NewLine );

				builder.Append( "SQL Error Source:      " );
				try
				{
					builder.Append( error.Source );
				}
				catch( Exception e )
				{
					builder.Append( e.Message );
				}
				builder.Append( Environment.NewLine );

				builder.Append( "SQL Error State:       " );
				try
				{
					builder.Append( error.State );
				}
				catch( Exception e )
				{
					builder.Append( e.Message );
				}
				builder.Append( Environment.NewLine );
				builder.Append( Environment.NewLine );
			}
		}

		/// <summary>
		/// retrieve relevant assembly details for this exception, if possible
		/// </summary>
		private string AssemblyInfoToString( Exception ex )
		{
			// ex.source USUALLY contains the name of the assembly that generated the exception
			// at least, according to the MSDN documentation..
			Assembly a = GetAssemblyFromName( ex.Source );

			if( a == null )
				return AllAssemblyDetailsToString();
			else
				return AssemblyDetailsToString( a );
		}

		/// <summary>
		/// matches assembly by Assembly.GetName.Name; returns nothing if no match
		/// </summary>
		private Assembly GetAssemblyFromName( string assemblyName )
		{
			foreach( Assembly a in AppDomain.CurrentDomain.GetAssemblies() )
			{
				if( a.GetName().Name == assemblyName )
					return a;
			}
			return null;
		}

		/// <summary>
		/// returns brief summary info for all assemblies in the current AppDomain
		/// </summary>
		private string AllAssemblyDetailsToString()
		{
			StringBuilder sb = new StringBuilder();
			const string strLineFormat = "    {0, -30} {1, -15} {2}";

			sb.Append( Environment.NewLine );
			sb.Append( String.Format( strLineFormat, "Assembly", "Version", "BuildDate" ) );
			sb.Append( Environment.NewLine );
			sb.Append( String.Format( strLineFormat, "--------", "-------", "---------" ) );
			sb.Append( Environment.NewLine );

			foreach( Assembly a in AppDomain.CurrentDomain.GetAssemblies() )
			{
				NameValueCollection nvc = AssemblyAttribs( a );
				// assemblies without versions are weird (dynamic?)
				if( nvc["Version"] != "0.0.0.0" )
				{
					sb.Append( String.Format( strLineFormat,
						System.IO.Path.GetFileName( nvc["CodeBase"] ), nvc["Version"], nvc["BuildDate"] ) );
					sb.Append( Environment.NewLine );
				}
			}

			return sb.ToString();
		}

		/// <summary>
		/// returns string name / string value pair of all attribs for the specified assembly
		/// </summary>
		/// <remarks>
		/// note that Assembly* values are pulled from AssemblyInfo file in project folder
		///
		/// Trademark       = AssemblyTrademark string
		/// Debuggable      = True
		/// GUID            = 7FDF68D5-8C6F-44C9-B391-117B5AFB5467
		/// CLSCompliant    = True
		/// Product         = AssemblyProduct string
		/// Copyright       = AssemblyCopyright string
		/// Company         = AssemblyCompany string
		/// Description     = AssemblyDescription string
		/// Title           = AssemblyTitle string
		/// </remarks>
		private NameValueCollection AssemblyAttribs( Assembly a )
		{
			string Name;
			string Value;
			NameValueCollection nvc = new NameValueCollection();

			foreach( object attrib in a.GetCustomAttributes( false ) )
			{
				Name = attrib.GetType().ToString();
				Value = "";

				switch( Name )
				{
				case "System.Diagnostics.DebuggableAttribute":
					Name = "Debuggable";
					Value = ( (System.Diagnostics.DebuggableAttribute)attrib ).IsJITTrackingEnabled.ToString();
					break;

				case "System.CLSCompliantAttribute":
					Name = "CLSCompliant";
					Value = ( (System.CLSCompliantAttribute)attrib ).IsCompliant.ToString();
					break;

				case "System.Runtime.InteropServices.GuidAttribute":
					Name = "GUID";
					Value = ( (System.Runtime.InteropServices.GuidAttribute)attrib ).Value.ToString();
					break;

				case "System.Reflection.AssemblyTrademarkAttribute":
					Name = "Trademark";
					Value = ( (AssemblyTrademarkAttribute)attrib ).Trademark.ToString();
					break;

				case "System.Reflection.AssemblyProductAttribute":
					Name = "Product";
					Value = ( (AssemblyProductAttribute)attrib ).Product.ToString();
					break;

				case "System.Reflection.AssemblyCopyrightAttribute":
					Name = "Copyright";
					Value = ( (AssemblyCopyrightAttribute)attrib ).Copyright.ToString();
					break;

				case "System.Reflection.AssemblyCompanyAttribute":
					Name = "Company";
					Value = ( (AssemblyCompanyAttribute)attrib ).Company.ToString();
					break;

				case "System.Reflection.AssemblyTitleAttribute":
					Name = "Title";
					Value = ( (AssemblyTitleAttribute)attrib ).Title.ToString();
					break;

				case "System.Reflection.AssemblyDescriptionAttribute":
					Name = "Description";
					Value = ( (AssemblyDescriptionAttribute)attrib ).Description.ToString();
					break;

				default:
					Console.WriteLine( Name );
					break;
				}

				if( !string.IsNullOrEmpty( Value ) )
				{
					if( string.IsNullOrEmpty( nvc[Name] ) )
						nvc.Add( Name, Value );
				}
			}

			// add some extra values that are not in the AssemblyInfo, but nice to have
			nvc.Add( "CodeBase", a.CodeBase.Replace( "file:///", "" ) );
			nvc.Add( "BuildDate", GetAssemblyBuildDate( a ).ToString() );
			nvc.Add( "Version", a.GetName().Version.ToString() );
			nvc.Add( "FullName", a.FullName );

			return nvc;
		}

		/// <summary>
		/// returns more detailed information for a single assembly
		/// </summary>
		private string AssemblyDetailsToString( Assembly a )
		{
			StringBuilder sb = new StringBuilder();
			NameValueCollection nvc = AssemblyAttribs( a );

			sb.Append( "Assembly Codebase:     " );

			try
			{
				sb.Append( nvc["CodeBase"] );
			}
			catch( Exception e )
			{
				sb.Append( e.Message );
			}

			sb.Append( Environment.NewLine );
			sb.Append( "Assembly Full Name:    " );

			try
			{
				sb.Append( nvc["FullName"] );
			}
			catch( Exception e )
			{
				sb.Append( e.Message );
			}

			sb.Append( Environment.NewLine );

			sb.Append( "Assembly Version:      " );
			try
			{
				sb.Append( nvc["Version"] );
			}
			catch( Exception e )
			{
				sb.Append( e.Message );
			}
			sb.Append( Environment.NewLine );

			sb.Append( "Assembly Build Date:   " );
			try
			{
				sb.Append( nvc["BuildDate"] );
			}
			catch( Exception e )
			{
				sb.Append( e.Message );
			}
			sb.Append( Environment.NewLine );

			return sb.ToString();
		}

		/// <summary>
		/// attempts to coerce the value object using the .ToString method if possible, 
		/// then appends a formatted key/value string pair to a StringBuilder. 
		/// will display the type name if the object cannot be coerced.
		/// </summary>
		protected void AppendLine( StringBuilder sb, string Key, object Value )
		{
			string strValue = "(NULL)";

			if( Value != null )
			{
				try
				{
					strValue = Value.ToString();
				}
				catch( Exception )
				{
					strValue = "(" + Value.GetType().ToString() + ")";
				}
			}

			AppendLine( sb, Key, strValue );
		}

		/// <summary>
		/// appends a formatted key/value string pair to a StringBuilder
		/// </summary>
		protected void AppendLine( StringBuilder sb, string Key, string strValue )
		{
			sb.Append( String.Format( "    {0, -30}{1}", Key, strValue ) );
			sb.Append( Environment.NewLine );
		}

		#region Enhanced Stack Trace
		/// <summary>
		/// enhanced stack trace generator, using current execution as start point
		/// </summary>
		protected string EnhancedStackTrace()
		{
			return EnhancedStackTrace( new StackTrace( true ), "ASPUnhandledException" );
		}

		/// <summary>
		/// enhanced stack trace generator, using existing exception as start point
		/// </summary>
		protected string EnhancedStackTrace( Exception ex )
		{
            return EnhancedStackTrace(new StackTrace(ex, true), null);
		}

		/// <summary>
		/// enhanced stack trace generator (prebuilt stack trace, class name to skip)
		/// </summary>
		/// <param name="objStackTrace"></param>
		/// <param name="strSkipClassName"></param>
		/// <returns></returns>
		protected string EnhancedStackTrace( StackTrace objStackTrace, string strSkipClassName )
		{
			System.Text.StringBuilder sb = new StringBuilder();

			sb.Append( Environment.NewLine );
			sb.Append( "---- Stack Trace ----" );
			sb.Append( Environment.NewLine );

			for( int intFrame = 0; intFrame < objStackTrace.FrameCount; intFrame++ )
			{
				StackFrame sf = objStackTrace.GetFrame( intFrame );
				MemberInfo mi = sf.GetMethod();

				if( !string.IsNullOrEmpty( strSkipClassName) && ( mi.DeclaringType.Name.IndexOf( strSkipClassName ) > -1 ) )
				{
					// don't include frames with this name
				}
				else
					sb.Append( StackFrameToString( sf ) );
			}
			sb.Append( Environment.NewLine );

			return sb.ToString();
		}
		#endregion
		#endregion
	}
}
